﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using VmcRemote.Common.Interfaces;
using System.Threading;

namespace VmcRemote.Common
{
    public abstract class ConnectionBase : IConnection
    {
        public const int DefaultTimeout = 5;

        private Timer inactivityTimer;
        private bool activity;
        private object activityLock = new object();

        public ConnectionBase(string host, int port) : this(host, port, DefaultTimeout) { }

        public ConnectionBase(string host, int port, int inactivityTimeout)
        {
            Host = host;
            Port = port;
            InactivityTimeout = inactivityTimeout;
            InitialiseConnection();
        }

        #region Abstract Methods
        protected abstract bool Connect();

        protected abstract bool Disconnect();

        protected abstract void Send(byte[] bytes);
        #endregion

        #region "On" Methods
        protected virtual void OnSendCommand(string commandText)
        {
            if (!string.IsNullOrEmpty(commandText))
            {
                ASCIIEncoding encoding = new ASCIIEncoding();
                byte[] bytes = encoding.GetBytes(commandText + (commandText.EndsWith("\r") ? string.Empty : "\r"));
                Send(bytes);
            }
        }

        protected virtual void OnInitialiseConnection()
        {
            Status = ConnectionStatus.Connecting;

            if (Connect())
                Status = ConnectionStatus.Open;
            else
                Status = ConnectionStatus.Closed;
        }

        protected virtual void OnCloseConnection()
        {
            if (Disconnect())
            {
                Status = ConnectionStatus.Closed;
                StopTimer();
            }
        }

        protected virtual void OnConnectionStatusChanged()
        {
            if (ConnectionStatusChanged != null)
                ConnectionStatusChanged(this, new ConnectionStatusChangedEventArgs { Status = this.Status });
        }
        #endregion

        #region Internal Implementation
        private void InitialiseConnection()
        {
            lock (activityLock)
            {
                activity = true;
                CloseConnection();

                if (Status == ConnectionStatus.Closed)
                    OnInitialiseConnection();

                if (Status == ConnectionStatus.Open && inactivityTimer == null)
                    inactivityTimer = new Timer(timer_Tick, null, InactivityTimeout * 1000, InactivityTimeout * 1000);
            }
        }

        private void CloseConnection()
        {
            if (Status != ConnectionStatus.Open) return;
            lock (activityLock)
            {
                OnCloseConnection();
            }
        }

        private void SendCommandInternal(string commandText, bool retryOnFailure)
        {
            lock (activityLock)
            {
                activity = true;
                if (Status == ConnectionStatus.Closed) InitialiseConnection();
                try
                {
                    if (!string.IsNullOrEmpty(commandText))
                    {
                        OnSendCommand(commandText);
                    }
                }
                catch
                {
                    if (retryOnFailure)
                        SendCommandInternal(commandText, false);
                    else
                        throw;
                }
            }
        }
        #endregion

        #region Inactivity Timer Methods
        private void StopTimer()
        {
            if (inactivityTimer != null)
                inactivityTimer.Change(Timeout.Infinite, Timeout.Infinite);
        }

        private void StartTimer()
        {
            if (inactivityTimer != null)
                inactivityTimer.Change(InactivityTimeout * 1000, InactivityTimeout * 1000);
        }

        private void timer_Tick(object state)
        {
            if (!activity)
            {
                lock (activityLock)
                {
                    if (!activity)
                    {
                        CloseConnection();
                    }
                }
            }
            activity = false;
        }
        #endregion

        #region IConnection Members

        public string Host
        {
            get;
            private set;
        }

        public int Port
        {
            get;
            private set;
        }

        public int InactivityTimeout
        {
            get;
            private set;
        }

        private ConnectionStatus status = ConnectionStatus.Closed;
        public ConnectionStatus Status
        {
            get { return status; }
            private set
            {
                if (status != value)
                {
                    status = value;
                    OnConnectionStatusChanged();
                    if (status == ConnectionStatus.Closed)
                        StopTimer();
                    else
                        StartTimer();
                }
            }
        }

        public event EventHandler<ConnectionStatusChangedEventArgs> ConnectionStatusChanged;

        public void SendCommand(string commandText)
        {
            SendCommandInternal(commandText, true);
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (inactivityTimer != null)
                    inactivityTimer.Dispose();
                inactivityTimer = null;
                CloseConnection();
            }
        }

        ~ConnectionBase()
        {
            Dispose(false);
        }

        #endregion
    }
}
